use darling::FromDeriveInput;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};
use proc_macro2::TokenStream as T2;

use crate::periph::{rcc_enable, RccAxB};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(io_config))]
struct Attr {
    name: String,
    mode: String,
}

// AIN, OUT_PP, OUT_OD, IN_FLOAT, IN_UP, IN_DOWN, AF_PP, AF_OD

#[derive(Debug)]
enum Mode {
    Ain,
    OutPP,
    OutOD,
    InputFloat,
    InputUp,
    InputDown,
    AfPP,
    AfOD
}

#[derive(Debug)]
struct Port {
    pin: u8,
    port: String,
}

impl From<&str> for Port {
    fn from(value: &str) -> Self {
        let name = &value[1..2];
        let pin = &value[2..];
        Self { pin: pin.parse().unwrap(), port: name.to_string() }
    }
}

impl From<&str> for Mode {
    fn from(value: &str) -> Self {
        match value {
            "AIN" => Self::Ain,
            "OUT_PP" => Self::OutPP,
            "OUT_OD" => Self::OutOD,
            "IN_FLOAT" => Self::InputFloat,
            "IN_UP" => Self::InputUp,
            "IN_DOWN" => Self::InputDown,
            "AF_PP" => Self::AfPP,
            "AF_OD" => Self::AfOD,
            _ => panic!("invalid mode")
        }
    }
}

impl Attr {

    fn into(self) -> Attr2 {
        Attr2 { port: Port::from(self.name.as_ref()), mode: Mode::from(self.mode.as_ref()) }
    }

}

#[derive(Debug)]
struct Attr2 {
    port: Port,
    mode: Mode,
}

impl Attr2 {

    fn enable_rcc(&self) -> T2 {
        let field = format_ident!("iop{}en", self.port.port.to_lowercase());
        rcc_enable(RccAxB::APB2, &field, false)
    }

    fn config(&self) -> T2 {
        let gpio_up = format_ident!("GPIO{}", self.port.port.to_uppercase());
        let cr = if self.port.pin < 8 {
            format_ident!("crl")
        } else {
            format_ident!("crh")
        };
        let mode = format_ident!("mode{}", self.port.pin);
        let cnf = format_ident!("cnf{}", self.port.pin);

        let mode_value = match self.mode {
            Mode::Ain |
            Mode::InputFloat |
            Mode::InputUp |
            Mode::InputDown => format_ident!("input"),
            
            Mode::OutPP | 
            Mode::AfPP | 
            Mode::AfOD |
            Mode::OutOD => format_ident!("output50"),
        };

        let cnf_value = match self.mode {
            Mode::Ain | 
            Mode::OutPP => format_ident!("push_pull"),
            
            Mode::InputFloat |
            Mode::OutOD => format_ident!("open_drain"),
        
            Mode::InputUp |
            Mode::InputDown |
            Mode::AfPP => format_ident!("alt_push_pull"),

            Mode::AfOD => format_ident!("alt_open_drain")
        };

        let odr = match self.mode {
            Mode::InputUp => {
                let bs = format_ident!("bs{}", self.port.pin);
                quote! {
                    gpio.bsrr.write(|w| {
                        w.#bs().set()
                    });
                }
            },

            Mode::InputDown => {
                let br = format_ident!("br{}", self.port.pin);
                quote! {
                    gpio.brr.write(|w| {
                        w.#br().reset()
                    });
                }
            }

            _ => quote! {}
        };

        quote! {
            use stm32f1::stm32f103::#gpio_up;
            let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
            gpio.#cr.modify(|_, w| {
                w.#mode().#mode_value()
                .#cnf().#cnf_value()
            });
            #odr
        }
    }

    fn set(&self) -> T2 {
        let gpio_up = format_ident!("GPIO{}", self.port.port.to_uppercase());
        let bs = format_ident!("bs{}", self.port.pin);
        quote! {
            pub fn set() {
                use stm32f1::stm32f103::#gpio_up;
                let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
                gpio.bsrr.write(|w| {
                    w.#bs().set()
                });
            }
        }
    }

    fn reset(&self) -> T2 {
        let gpio_up = format_ident!("GPIO{}", self.port.port.to_uppercase());
        let br = format_ident!("br{}", self.port.pin);
        quote! {
            pub fn reset() {
                use stm32f1::stm32f103::#gpio_up;
                let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
                gpio.brr.write(|w| {
                    w.#br().reset()
                });
            }
        }
    }

    fn toggle(&self) -> T2 {
        let gpio_up = format_ident!("GPIO{}", self.port.port.to_uppercase());
        let odr = format_ident!("odr{}", self.port.pin);
        quote! {
            pub fn toggle() {
                use stm32f1::stm32f103::#gpio_up;
                let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
                gpio.odr.modify(|r, w| {
                    w.#odr().bit(!r.#odr().bit())
                });
            }
        }
    }

    fn read(&self) -> T2 {
        let gpio_up = format_ident!("GPIO{}", self.port.port.to_uppercase());
        let idr = format_ident!("idr{}", self.port.pin);
        quote! {
            pub fn read() -> bool {
                use stm32f1::stm32f103::#gpio_up;
                let gpio: #gpio_up = unsafe { ::core::mem::transmute(()) };
                gpio.idr.read().#idr().is_high()
            }
        }
    }
}

pub fn build(input: TokenStream) -> TokenStream {

    let input = parse_macro_input!(input as DeriveInput);
    let attr = Attr::from_derive_input(&input).unwrap().into();
    let name = &input.ident;

    let rcc = attr.enable_rcc();
    let config = attr.config();

    let set = attr.set();
    let reset = attr.reset();
    let toggle = attr.toggle();
    let read = attr.read();

    let expand = quote! {
        
        impl #name {

            pub fn gpio_init() {
                #rcc
                #config
            }
            #set
            #reset
            #toggle
            #read
        } 
    };
    expand.into()
}






